// Copyright (c) 2025 Shenzhen Kaihong Digital Industry Development Co., Ltd.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::path::PathBuf;

use anyhow::Context;
use clap::Parser;
use log::info;
use petgraph::graph::NodeIndex;

use crate::{build::BuildContext, commands::BuildGraphArgs, database::deploy_stat::DeployStat, deploy::{collect_deploy_deps, deploy_pkgs, filter_deployed_pkgs, install_deploy_runtime_deps, MergeMode}, Arch, FerriumCommand};

#[derive(Debug, Parser)]
pub struct DeployCommand {
    #[command(flatten)]
    build_graph_args: BuildGraphArgs,
    /// Merge mode for deploy
    #[arg(long, value_enum, default_value_t = MergeMode::HardLink)]
    merge_mode: MergeMode,
    /// The root path to deploy
    #[arg(long, default_value = "/")]
    root: PathBuf,
    /// The target architecture
    #[arg(long)]
    arch: Option<Arch>,
    /// Deploy pkgs only the same arch specified by '--arch'
    #[arg(long)]
    single_arch: bool,
}

impl FerriumCommand for DeployCommand {
    async fn run(self, multi_progress: indicatif::MultiProgress) -> anyhow::Result<()> {
        let root = std::path::absolute(&self.root)
            .context(format!("failed to absolute '{}'", self.root.display()))?;
        let (mut dep_graph, toolchain_name, mut system_config) = self.build_graph_args.build_graph().await?;
        let sorted_ids = dep_graph.verify(true)
            .context("failed to resolve specs")?;

        let hashes = BuildContext::spec_hashes(&dep_graph, &sorted_ids).await?;
        let spec_strings = BuildContext::prepare_spec_strings(&hashes, &dep_graph);
        let to_deploy = collect_deploy_deps(
            &dep_graph,
            dep_graph.id_map.specs().collect(),
        );
        let toolchain_config = std::mem::take(system_config.toolchains.0.get_mut(&toolchain_name).unwrap());
        let (dep_graph, toolchain_config) = install_deploy_runtime_deps(
            &to_deploy,
            &spec_strings,
            dep_graph,
            sorted_ids,
            toolchain_config,
            &system_config,
            multi_progress,
        ).await.context("failed to install to-deploy pkgs")?;
        let to_deploy = filter_deployed_pkgs(to_deploy, &spec_strings, &root).await?;

        let arch = self.arch.unwrap_or(Arch::host());
        info!("Deploying packages...");
        deploy_pkgs(
            &root,
            &dep_graph,
            &spec_strings,
            &toolchain_config.triple_envs,
            &[],
            to_deploy.ones().map(NodeIndex::new),
            arch,
            false,
            self.merge_mode,
            self.single_arch,
        ).await.context("failed to deploy")?;

        let deploy_stat = DeployStat {
            mode: Some(self.merge_mode),
            arch,
            pkgs: to_deploy.ones().map(|id| spec_strings[id].clone()).collect(),
        };
        deploy_stat.save_after_current(root).await
            .context("failed to save deploy stat")?;

        Ok(())
    }
}
